package com.nio.c5_网络编程.selector.messageSide;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * @version No one is coming, unless you change yourself!!!
 * @author： pang-yun
 * @date： 2021-06-01 14:03
 */

/*
 * - 用于解决 消息边界问题  因为消息会有大有小  allocate 可能放不下
 *
 * 方法1： 可以约定消息的 最大长度   有时候 消息较小  会浪费带宽
 * 方法2： 每个消息用 “ \n” 分隔   效率低   因为要不断去对比  找到 \n
 * 方法3： 把消息分成两个部分  前面是：消息的长度（或者包含类型）  后一部分： 消息的内容！
 *
 * */

/*
 * 需要解决的问题：  消息太长   bytebuffer  要进行扩容的问题  否则只能读取到后半部分
 *     bytebuffer 又不能提升全局  因为可能多个 SocketChannel 使用 造成混乱
 *       需要使用附件的方式来  来注册进来
 *
 *      流程：
 *      1. 发送一个很长的消息进来  此是 split 方法没有 找到 ‘\n’  没有读取  compact之后  position=limit  因此需要扩容
 *          将 byteBuffer 经flip  模式后 写入 扩容的 buffer 之中  然后将新的附件 放到 key上
 * */



@Slf4j
public class MessageSideServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);


        SelectionKey sscKey = ssc.register(selector, SelectionKey.OP_ACCEPT, null); // 三者分别为 selector 触发类型  附件
        //sscKey.interestOps(SelectionKey.OP_ACCEPT);

        ssc.bind(new InetSocketAddress(9999));

        while (true) {
            selector.select();

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                log.debug("key: {}", key);

                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);

                    // 将 byteBuffer 作为附件关联到 selectionKey   每一个SocketChannel  就拥有一个自身对应的 bytebuffer
                    ByteBuffer buffer = ByteBuffer.allocate(16);  // attachment
                    SelectionKey scKey = sc.register(selector, 0, buffer);
                    scKey.interestOps(SelectionKey.OP_READ);

                } else if (key.isReadable()) {
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        //     ByteBuffer byteBuffer = ByteBuffer.allocate(16);  //消息比较短时没有问题  消息长时怎么办
                        //超出长度时，不会报错 而是会触发两次读取,  如输入 Hello,World!,I am Pang Chao Jie
                        //m Pang Chao Jie  只能接收到 后一次的数据信息  如果发现 容量不够 就要先进性扩容

                        //从key中获取 attachment
                        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                        int read = channel.read(byteBuffer);
                        if (read == -1) {
                            key.cancel();
                        } else {
                            split(byteBuffer);
                            //容量不够时 需要扩容成为两倍
                            if (byteBuffer.position() == byteBuffer.limit()) {
                                ByteBuffer newBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
                                byteBuffer.flip();   // compact 后是写模式  要改为读模式
                                newBuffer.put(byteBuffer);
                                //关联新的附件
                                key.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }

                }

            }
        }

    }


    public static void split(ByteBuffer byteBuffer) {
        /*黏包的解析思路，找到“\n”，然后将他们分离
         * 新建一个ByteBuffer 将将\n之前的内容写入到新的ByteBuffer中*/

        byteBuffer.flip();
        for (int i = 0; i < byteBuffer.limit(); i++) {
            if (byteBuffer.get(i) == '\n') {
                //找到完整语句的长度   如果没有 \n   就不会有输出

                int length = i + 1 - byteBuffer.position();

                ByteBuffer newBuffer = ByteBuffer.allocate(length);
                //将整个语句放入新建的ByteBuffer中
                for (int j = 0; j < length; j++) {
                    //从原有的byteBuffer将内容读取出来
                    byte b = byteBuffer.get();
                    //写入newBuffer中
                    newBuffer.put(b);
                }
                read(newBuffer);
            }

        }
        byteBuffer.compact();
    }

    public static void read(ByteBuffer buffer) {
        buffer.flip();
        while (buffer.hasRemaining()) {
            byte b = buffer.get();
            System.out.print((char) b);
        }
        buffer.clear();

    }
}
